/*----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the 
License at http://www.mozilla.org/MPL/
	
Software distributed under the License is distributed on 
an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, 
either express or implied. 
See the License for the specific language governing rights and 
limitations under the License.
	
The Original Code is Fever RS ( ActionScript 3 ) Framework code.
	
The Initial Developer of the Original Code is Romain Ecarnot.
Portions created by Initial Developer are Copyright (C) 2006
the Initial Developer. All Rights Reserved.
	
Contributor(s):
	
Unless required by applicable law or agreed to in writing, 
software distributed under the License is distributed on 
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
OF ANY KIND, either express or implied.
See the License for the specific language governing permissions 
and limitations under the License.
----------------------------------------------------------------------------*/

package fever.ui.keys 
{
	import flash.events.Event;	import flash.events.KeyboardEvent;	import flash.utils.clearInterval;	import flash.utils.getQualifiedClassName;	import flash.utils.setInterval;		import com.bourre.collection.HashMap;	import com.bourre.commands.Command;	import com.bourre.core.CoreFactory;	import com.bourre.events.EventBroadcaster;		import fever.core.RSCore;	import fever.events.KeyShortcutEvent;	import fever.utils.ArrayUtil;	import fever.utils.Stringifier;		/**
	 * Manages and stores <strong>KeyShortcut</strong> references.
	 * 
	 * <p><strong>KeyShortcut</strong> can not be instanciate directly; use 
	 * <strong>KeyShortcutMapLocator</strong>.getKeyMap() to build instance.
	 * 
	 * @example
	 * <listing version="3.0">
	 * 
	 *   var m : KeyShortcutMap = KeyShortcutMapLocator.getKeyMap( "myMap" );
	 * </listing>
	 * 
	 * @includeExample KeySample.as -noswf
	 * 
	 * @see fever.ui.keys.KeyConstants	 * @see fever.ui.keys.KeyShortcut	 * @see fever.ui.keys.KeyShortcutMapLocator
	 * 
	 * @author Romain Ecarnot
	 */
	public class KeyShortcutMap
	{
		//--------------------------------------------------------------------
		// Private properties
		//--------------------------------------------------------------------
		
		private var _id : String;
		private var _activated : Boolean;
				private var _oEB : EventBroadcaster;
		private var _mEventList : HashMap;
		private var _mShortcutList : HashMap;
		private var _keyDownList : Array;
		
		private var _isPending : Boolean;
		private var _isPendingCombo : Boolean;
		private var _pendingComboInterval : Number;
		private var _pendingInterval : Number;
		
		
		//--------------------------------------------------------------------
		// Public properties
		//--------------------------------------------------------------------
		
		/**
		 * Defines if we listen the <code>KeyUp</code> event or not.
		 * 
		 * <p>Key combination ( combo ) are not available if 
		 * <code>true</code></p>
		 * 
		 * @default false
		 */
		public var listenKeyUpEvent : Boolean;
		
		/** <strong>KeyShortcutMap</strong> identifier. */
		public function get id( ) : String { return _id; }
		
		
		//--------------------------------------------------------------------
		// Public API
		//--------------------------------------------------------------------
		
		/**
		 * @private
		 * Builds new <strong>KeyShortcutMap</strong> instance.
		 * 
		 * <p>Can only be called by 
		 * <code>KeyShortcutMapLocator.getKeyMap()</code> method.</p>
		 */
		internal static function buildInstance( id : String ) 
			: KeyShortcutMap
		{
			return new KeyShortcutMap( new PrivateConstructorAccess(), id );	
		}
		
		/**
		 * Loads current map.
		 * 
		 * <p>All stored <strong>KeyShortcut</strong> become active.</p>
		 */
		public function load( ) : void
		{
			if ( !_activated )
			{
				_activated = true;
				_toggleKeyEventListener( true );
				
				_keyDownList = new Array();
			}
		}
		
		/**
		 * Unloads current map.
		 * 
		 * <p>All stored <strong>KeyShortcut</strong> become inactive.</p>
		 */
		public function unload( ) : void
		{
			if ( _activated )
			{
				_activated = false;
				_toggleKeyEventListener( false );
				
				_keyDownList = new Array();
			}
		}
		
		/**
		 * Adds <code>command</code> Command as <code>key</code> listener.
		 * 
		 * @example 
		 * <listing version="3.0">
		 * 
		 * var map : KeyShortcutMap = KeyShortcutMapLocator.getKeyMap( 'MyMap' );
		 * map.addCommand( new Delegate ( _test ), KeyConstants.onKeyY );
		 * map.addCommand( new Delegate ( _test ), KeyConstants.onKeyCONTROL, KeyConstants.onKeyY );
		 * map.addCommand( new Delegate ( _test ), new KeyShortcut( KeyConstants.onKeyX ) );
		 * map.load(); 
		 * </listing>
		 * 
		 * @param command Command instance
		 * @param key KeyShortcut to listen
		 * @param rest KeyShortcut combination
		 */
		public function addCommand( command : Command, key : KeyShortcut, 
			...rest ) : KeyShortcut
		{
			var storedKey : KeyShortcut = key;
			
			if( rest.length > 0 )
			{
				storedKey = _createShortcutFromCombo( [ key ].concat( rest ) );
			}
			
			if( _mEventList.isEmpty() ) _toggleKeyEventListener( true );
			
			_mShortcutList.put( storedKey.combo, storedKey );
			_mEventList.put( storedKey.combo, command );
			
			return storedKey;
		}
		
		/**
		 * Removes <code>key</code> KeyShortcut from map.
		 * 
		 * @example 
		 * <listing version="3.0">
		 * 
		 * var key : KeyShortcut = new KeyShortcut( KeyConstants.onKeyX );
		 * 
		 * var map : KeyShortcutMap = KeyShortcutMapLocator.getKeyMap( 'MyMap' );
		 * map.remove( KeyConstants.onKeyY );
		 * map.remove( KeyConstants.onKeyCONTROL, KeyConstants.onKeyY );		 * map.remove( key );
		 * </listing>
		 * 
		 * @param key KeyShortcut to listen
		 * @param rest KeyShortcut combination to remove
		 */
		public function remove( key : KeyShortcut, ...rest ) : KeyShortcut
		{
			var storedKey : KeyShortcut = key;
			
			if( rest.length > 0 )
			{
				storedKey = _createShortcutFromCombo( [ key ].concat( rest ) );
			}
			
			_mEventList.remove( storedKey.combo );
			if( _mEventList.isEmpty() ) _toggleKeyEventListener( false );
			
			return _mShortcutList.remove( storedKey.combo ) as KeyShortcut;
		}
		
		/**
		 * Clears shortcuts list.
		 */
		public function clear( ) : void
		{
			_mEventList.clear();
			_mShortcutList.clear();
			
			_toggleKeyEventListener( false );
		}
		
		/**
		 * Broadcasts specific shortcut.
		 * 
		 * @example
		 * <listing version="3.0">
		 * 
		 * var combo : KeyShortcut = new KeyShortcut( 
		 * 	KeyConstants.onKeyCONTROL, 
		 * 	KeyConstants.onKeyD 
		 * );
		 * 
		 * var map : KeyShortcutMap = KeyShortcutMapLocator.getKeyMap( 'MyMap' );
		 * map.addCommand( new Delegate( _test ), combo );		 * map.addCommand( 
		 * 	new Delegate( _test ), 
		 * 	KeyConstants.onKeyCONTROL, 
		 * 	KeyConstants.onKeyD 
		 * );
		 * 
		 * //Trigger shortcut
		 * map.fireShortcut( combo );
		 * 
		 * //Trigger the same shortcut using combination
		 * map.fireShortcut( 
		 * 	 KeyConstants.onKeyCONTROL, 
			 KeyConstants.onKeyD 
		 * );
		 * </listing>
		 * 
		 * @param key Shortcut to activate
		 * @param rest Shortcut key combination 
		 */
		public function fireShortcut( key : KeyShortcut, ...rest ) : void
		{
			var keyType : String = "";
			
			if( rest.length > 0 )
			{
				rest = [ key ].concat( rest );
				
				var l : int = rest.length;
				
				for( var i : int = 0; i < l; i+= 1 )
				{
					var next : KeyShortcut = rest[ i ] as KeyShortcut;
					keyType += next.combo;
					
					if( i < l-1 ) keyType += KeyShortcut.LIMITER;
				}
			}
			else keyType = key.combo;
			
			_fireEvent( keyType, NaN, null );
		}
		
		/**
		 * Returns string representation.
		 */
		public function toString() : String
		{
			return Stringifier.parse( this );
		}
		

		//--------------------------------------------------------------------
		// Private implementations
		//--------------------------------------------------------------------
		
		/**
		 * @private
		 * Private constructor
		 */		
		public function KeyShortcutMap( access : PrivateConstructorAccess, 
			id : String )
		{
			_id = id;
			
			_oEB = new EventBroadcaster(this );
			_oEB.addListener( this );
			
			_mEventList = new HashMap();
			_mShortcutList = new HashMap();
			_keyDownList = new Array();
			
			_isPending = false;
			_isPendingCombo = false;
			listenKeyUpEvent = false;
			
			_activated = false;
		}
		
		private function _createShortcutFromCombo( keyList : Array ) : KeyShortcut
		{
			return CoreFactory.buildInstance( 
					getQualifiedClassName( KeyShortcut ), 
					keyList 
				) as KeyShortcut;
		}
		
		private function _toggleKeyEventListener( enabled : Boolean = false ) 
			: void
		{
			if( enabled && _activated )
			{
				RSCore.stage.addEventListener( 
					KeyboardEvent.KEY_DOWN, _keyDownHandler );	
				RSCore.stage.addEventListener( 
					KeyboardEvent.KEY_UP, _keyUpHandler );
			}
			else
			{
				RSCore.stage.removeEventListener( 
					KeyboardEvent.KEY_DOWN, _keyDownHandler );	
				RSCore.stage.removeEventListener( 
					KeyboardEvent.KEY_UP, _keyUpHandler );
			}
		}
		
		private function _keyDownHandler( event : KeyboardEvent ) : void
		{
			if( !listenKeyUpEvent )
			{
				var code : uint = event.keyCode;
				
				var shortcut : KeyShortcut = KeyShortcut.getStruct( code );
				
				if( !ArrayUtil.contains( shortcut.combo,  _keyDownList ) )
				{
					_keyDownList.push( shortcut.combo );
				}
				
				var key : String = _keyDownList.join( KeyShortcut.LIMITER );
				
				if( _mShortcutList.containsKey( key ) )
				{
					if( !_isPendingCombo )
					{
						var myKey : KeyShortcut = _mShortcutList.get( key );
						
						_fireEvent( myKey.combo, NaN, event );
						_isPendingCombo = true;
						clearInterval( _pendingComboInterval );
						_pendingComboInterval = setInterval( 
							_stopPendingCombo, myKey.delay );
					}	
				}
				else
				{
					if( !_isPending )
					{
						_isPending = true;
						_fireEvent( shortcut.combo, code, event );
						clearInterval(_pendingInterval);
						_pendingInterval = setInterval( 
							_stopPending, shortcut.delay );
					}
				}
			}
		}
		
		private function _keyUpHandler( event : KeyboardEvent ) : void
		{
			var code : uint = event.keyCode;
			
			ArrayUtil.remove( 
				KeyShortcut.getStruct( code ).combo, 
				_keyDownList 
			);
			
			if( !listenKeyUpEvent )
			{
				_stopPending();
				if( _isPendingCombo ) _stopPendingCombo();
			}
			if( listenKeyUpEvent ) _prepareEvent( code, event );
		}
		
		private function _prepareEvent( code : uint, 
			event : KeyboardEvent ) : void
		{ 
			_fireEvent( KeyShortcut.getStruct( code ).combo, code, event );
		}
		
		private function _fireEvent( comboID : String, code : uint, 
			event : KeyboardEvent = null ) : void 
		{
			if( !_activated ) return;
			
			if( _mEventList.containsKey( comboID ) )
			{
				var e : KeyShortcutEvent = new KeyShortcutEvent( 
					_mShortcutList.get( comboID ), this
				);
				e.keyCode = code;
				
				if( event != null )
				{
					e.charCode = event.charCode;
					e.altKey = event.altKey;
					e.ctrlKey = event.ctrlKey;
					e.shiftKey = event.shiftKey;
					e.keyLocation = event.keyLocation;
				}
				
				_oEB.broadcastEvent( e );
			}
		}
		
		/**
		 * Release key down repetition locker.
		 */
		private function _stopPending() : void
		{
			clearInterval(_pendingInterval);
			_isPending = false;
		}
		
		/**
		 * Release combo key down repetition locker.
		 */
		private function _stopPendingCombo() : void
		{
			clearInterval(_pendingComboInterval);
			
			_keyDownList = new Array(); 
			
			_isPendingCombo = false;
		}
		
		/**
		 * @private
		 * Process event handling
		 */
		final public function handleEvent( event : Event ) : void
		{
			var type : String = event.type.toString();
			
			if( _mEventList.containsKey( type ) )
			{
				var o : Object = _mEventList.get( type );
				
				if ( o is Command )
				{	
					o.execute( event );
				}
			}
		}
	}
}

internal class PrivateConstructorAccess 
{
}
